/*****************************************************************************
  Copyright ?2002-2004 by Martin Cook. All rights are reserved. If you like
  this code then feel free to go ahead and use it. The only thing I ask is 
  that you don't remove or alter my copyright notice. Your use of this 
  software is entirely at your own risk. I make no claims about the 
  reliability or fitness of this code for any particular purpose. If you 
  make changes or additions to this code then please clearly mark your code 
  as yours. If you have questions or comments then please contact me at: 
  martin@codegator.com
  
  Have Fun! :o)
*****************************************************************************/

using System;
using System.Data;
using System.Security.Principal;
using System.Threading;

using CG.Security.Configuration;
using CG.Security.Data;
using CG.Security.Principal;

namespace CG.Security
{

	/// <summary>
	/// Provides static methods that supply helper utilities for managing users.
	/// </summary>
	public sealed class UserManager
	{
		
		// ******************************************************************
		// Attributes.
		// ******************************************************************

		#region Attributes

		/// <summary>
		/// The data object used to interface with the database.
		/// </summary>
		private static IUserData c_userData;

		#endregion

		// ******************************************************************
		// Constructors.
		// ******************************************************************

		#region Constructors

		/// <summary>
		/// Class constructor.
		/// </summary>
		static UserManager()
		{
			c_userData = DataManager.UserData;
		} // End UserManager()

		#endregion

		// ******************************************************************
		// Public methods.
		// ******************************************************************

		#region Public methods

		/// <summary>
		/// Creates a new system-level user. Note that the calling user must
		/// be a member of the 'Admin' role in order to perform this action.
		/// </summary>
		/// <param name="userName">The user name (must be unique).</param>
		/// <param name="userPwd">The user password (must be hashed).</param>
		/// <returns>The identifier for the new user.</returns>
		public static int Create(
			string userName,
			string userPwd
			)
		{

			// Check the role of the user before we proceed.
			if (!Thread.CurrentPrincipal.IsInRole("Admin"))
				throw new SecurityException("User must be in the Admin role to perform this action!");

			// Should we create a hash for the password?
			if (userPwd.Length > 0)
				userPwd = SecurityManager.HashData(userPwd);
            			
			// Create the user in the database.
			return c_userData.Create(
				userName,
				userPwd
				);

		} // End Create()

		// ******************************************************************

		/// <summary>
		/// Deletes a system-level user. Note that the calling user must
		/// be a member of the 'Admin' role in order to perform this action.
		/// </summary>
		/// <param name="userID">The identifier for the user.</param>
		public static void Delete(
			int userID
			)
		{

			// Check the role of the user before we proceed.
			if (!Thread.CurrentPrincipal.IsInRole("Admin"))
				throw new SecurityException("User must be in the Admin role to perform this action!");

			// Delete the user in the database.
			c_userData.Delete(
				userID
				);

		} // End Delete()

		// ******************************************************************

		/// <summary>
		/// Updates the user name for the specified user. Note that the 
		/// calling user must be a member of the 'Admin' role in order to 
		/// perform this action.
		/// </summary>
		/// <param name="userID">The identifier for the user.</param>
		/// <param name="userName">The new user name (must be unique).</param>
		public static void Update(
			int userID,
			string userName
			)
		{

			// Check the role of the user before we proceed.
			if (!Thread.CurrentPrincipal.IsInRole("Admin"))
				throw new SecurityException("User must be in the Admin role to perform this action!");

			// Update the user in the database.
			c_userData.Update(
				userID,
				userName
				);

		} // End Update()

		// ******************************************************************

		/// <summary>
		/// Updates the password for the specified user. Note that if the 
		/// calling user is attempting to update a password for another user 
		/// then they must be a member of the 'Admin' role in order to perform
		/// this action.
		/// </summary>
		/// <param name="userID">The identifier for the user.</param>
		/// <param name="oldUserPwd">The existing user password (must be hashed).</param>
		/// <param name="newUserPwd">The new user password (must be hashed).</param>
		public static int UpdatePassword(
			int userID,
			string oldUserPwd,
			string newUserPwd
			)
		{

			// Check that the user is authenticated before we proceed.
			if (!Thread.CurrentPrincipal.Identity.IsAuthenticated)
				throw new SecurityException("User must be authenticated in order to perform this action!");

			// If the calling application is using our identity class, then 
			//   we can perform some additional verification.
			if (Thread.CurrentPrincipal.Identity.AuthenticationType == "Custom")
			{

				// Get the identity class.
				CustomIdentity identity = (CustomIdentity)Thread.CurrentPrincipal.Identity;

				// If the user isn't attempting to change their own password then we
				//   should verify that they are acting as an administrator before we
				//   proceed.
				if (identity.UserID != userID)
				{

					// Check the role of the user before we proceed.
					if (!Thread.CurrentPrincipal.IsInRole("Admin"))
						throw new SecurityException("User must be in the Admin role to perform this action!");

				} // End if the user should be an administrator.

			} // End if we should verify the identity/role of the user.
            
			// Should we create a hash for the old password?
			if (oldUserPwd.Length > 0)
				oldUserPwd = SecurityManager.HashData(oldUserPwd);

			// Should we create a hash for the new password?
			if (newUserPwd.Length > 0)
				newUserPwd = SecurityManager.HashData(newUserPwd);

			// Update the password in the database.
			return c_userData.UpdatePassword(
				userID,
				oldUserPwd,
				newUserPwd
				);

		} // End UpdatePassword()

		// ******************************************************************

		/// <summary>
		/// Returns all the system-level users in the database.
		/// </summary>
		/// <returns>A DataSet containing all the users.</returns>
		public static System.Data.DataSet FindAll()
		{

			// Get the list of users from the database.
			DataSet ds = c_userData.FindAll();

			// Ensure the dataset has a consistant name.
			ds.DataSetName = "UserData";

			// Ensure the table has a consistant name.
			ds.Tables[0].TableName = "cg_security_user";

			return ds;

		} // End FindAll()

		// ******************************************************************

		/// <summary>
		/// Returns the properties for the specified user.
		/// </summary>
		/// <param name="userID">The identifier for the user.</param>
		/// <returns>A DataReader containing the properties for the specified user.</returns>
		public static System.Data.IDataReader FindByPK(
			int userID
			)
		{

			// Look for the user in the database.
			return c_userData.FindByPK(
				userID
				);

		} // End FindByPK()

		// ******************************************************************

		/// <summary>
		/// Returns the properties for the specified user.
		/// </summary>
		/// <param name="userName">The name of the user.</param>
		/// <returns>A DataReader containing the properties for the specified user.</returns>
		public static System.Data.IDataReader FindByUserName(
			string userName
			)
		{

			// Look for the user in the database.
			return c_userData.FindByUserName(
				userName
				);

		} // End FindByUserName()

		#endregion

	} // End clas UserManager

} // End namespace CG.Security
